package llog

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	"gitee.com/lengyizhou/go-library/gpool"
)

const DATEFORMAT = "20060102"

const (
	levelInfo = iota
	levelWarning
	levelDebug
	levelError
)

const (
	useDate = iota
	useSize
)

const (
	Ldate         = 1 << iota     // 日期:  2009/01/23
	Ltime                         // 时间:  01:23:23
	Lmicroseconds                 // 微秒:  01:23:23.123123.
	Llongfile                     // 路径+文件名+行号: /a/b/c/d.go:23
	Lshortfile                    // 文件名+行号:   d.go:23
	LUTC                          // 使用标准的UTC时间格式
	LstdFlags     = Ldate | Ltime // 默认
)

type Logger struct {
	LogT *llogT
}

type llogT struct {
	mu       *sync.RWMutex
	prefix   string
	flags    int
	level    int
	depth    int
	wInfo    *log.Logger
	wDebug   *log.Logger
	wWarning *log.Logger
	wError   *log.Logger
	pool     *gpool.Pool

	size   int64      // split size
	ticker int        // split ticker
	st     int        // split type
	date   *time.Time // time point
	lf     *os.File   // log file
	fd     string     // log file dir
	fn     string     // log file name
	fc     int        // log file count
	suffix int        // log file suffix
}

func New(fileDir, fileName string, level, depth, flags, splitType int, maxSize int64) (*llogT, error) {
	if level < levelInfo || level > levelError {
		return nil, errors.New("log level error")
	}

	if splitType != useSize && splitType != useDate {
		return nil, errors.New("split log type error")
	}

	if splitType == useSize {
		if maxSize > 500 || maxSize < 0 {
			return nil, errors.New("split log size must > 0 && < 500(M)")
		}
	}

	err := os.MkdirAll(fileDir, 0755)
	if err != nil {
		return nil, err
	}
	now := time.Now()

	lt := &llogT{}
	lt.mu = new(sync.RWMutex)
	//	lt.prefix = prefix
	lt.level = level
	lt.depth = depth
	lt.flags = flags
	lt.pool = gpool.NewPool(16, 1024) //暂不支持配置
	lt.date = &now
	lt.size = maxSize * 1024 * 1024
	lt.ticker = 2 // seconds
	lt.st = splitType
	lt.fd = fileDir
	lt.fn = fileName
	lt.fc = 0
	lt.suffix = 0

	lt.mu.Lock()
	defer lt.mu.Unlock()

	if lt.st == useSize {
		lt.fc = lt.getFileCount()
		lt.fn = filepath.Join(lt.fd, lt.fn)

		for i := 0; i < lt.fc; i++ {
			_, err = os.Stat(lt.fn + "_" + strconv.Itoa(i) + ".log")
			if err != nil {
				if !os.IsNotExist(err) {
					return nil, err
				} else {
					break
				}
			} else {
				t := i + 1
				lt.suffix = t
			}
		}
		if !lt.isMustSplit() {
			err = lt.createLogger()
			if err != nil {
				return nil, err
			}
		} else {
			lt.split()
		}

	} else {
		lt.fn = filepath.Join(lt.fd, now.Format(DATEFORMAT)+".log")
		if !lt.isMustSplit() {
			_, err = os.Stat(lt.fn)
			if err != nil {
				if !os.IsNotExist(err) {
					return nil, err
				}
			}
			err = lt.createLogger()
			if err != nil {
				return nil, err
			}

		} else {
			lt.split()
		}
	}

	go lt.monitor()

	return lt, nil
}

func (lt *llogT) getFileCount() int {
	c := 0
	files, _ := ioutil.ReadDir(lt.fd)
	for _, f := range files {
		if strings.HasPrefix(f.Name(), lt.fn) {
			c++
		}
	}
	return c

}

func (lt *llogT) createLogger() error {
	var err error
	fn := lt.fn
	if lt.st == useSize {
		fn = lt.fn + "_" + strconv.Itoa(lt.suffix) + ".log"
	}
	lt.lf, err = os.OpenFile(fn, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		return err
	}
	lt.wInfo = log.New(lt.lf, "", lt.flags)
	lt.wDebug = log.New(lt.lf, "", lt.flags)
	lt.wWarning = log.New(lt.lf, "", lt.flags)
	lt.wError = log.New(lt.lf, "", lt.flags)
	lt.wInfo.SetPrefix("[INFO]")
	lt.wDebug.SetPrefix("[DEBUG]")
	lt.wWarning.SetPrefix("[WARNING]")
	lt.wError.SetPrefix("[ERROR]")
	return nil
}

func (lt *llogT) isMustSplit() bool {
	now := time.Now()
	if lt.st == useDate {
		t, _ := time.Parse(DATEFORMAT, now.Format(DATEFORMAT))
		if t.After(*lt.date) {
			return true
		}
	} else {
		if lt.fc > 1 {
			fn := lt.fn + "_" + strconv.Itoa(lt.suffix) + ".log"
			f, e := os.Stat(fn)
			if e == nil {
				if f.Size() >= lt.size {
					return true
				}
			}
		}
	}
	return false
}

func (lt *llogT) split() error {
	var err error
	if lt.st == useDate {
		if lt.lf != nil {
			lt.lf.Close()
		}
		now := time.Now()
		lt.fn = filepath.Join(lt.fd, now.Format(DATEFORMAT)+".log")
		err = lt.createLogger()
		if err != nil {
			return err
		}
		lt.date = &now
	} else {
		lt.suffix = lt.suffix + 1
		if lt.lf != nil {
			lt.lf.Close()
		}

		lfBak := lt.fn + "_" + strconv.Itoa(lt.suffix) + ".log"
		_, err = os.Stat(lfBak)
		if err == nil {
			os.Remove(lfBak)
		}
		os.Rename(lt.fn, lfBak)
		err = lt.createLogger()
		if err != nil {
			return err
		}
	}
	return nil
}

func (lt *llogT) check() {
	defer func() {
		if err := recover(); err != nil {
			lt.wError.Printf("Logger's check() catch panic: %v\n", err)
		}
	}()

	if lt.isMustSplit() {
		lt.mu.Lock()
		defer lt.mu.Unlock()
		lt.split()
	}
}

func (lt *llogT) monitor() {
	defer func() {
		if err := recover(); err != nil {
			lt.wError.Printf("Logger's monitor() catch panic: %v\n", err)
		}
	}()

	ticker := time.NewTicker(time.Duration(lt.ticker) * time.Second)
	for {
		select {
		case <-ticker.C:
			lt.check()
		}
	}
}

func (lt *llogT) Error(format string, v ...interface{}) {
	if levelError > lt.level {
		return
	}
	lt.pool.JobQueue <- func() {
		lt.wError.Output(lt.depth, fmt.Sprintf(format, v...))
	}
}

func (lt *llogT) Debug(format string, v ...interface{}) {
	if levelDebug > lt.level {
		return
	}
	lt.pool.JobQueue <- func() {
		lt.wDebug.Output(lt.depth, fmt.Sprintf(format, v...))
	}
}

func (lt *llogT) Warning(format string, v ...interface{}) {
	if levelWarning > lt.level {
		return
	}
	lt.pool.JobQueue <- func() {
		lt.wWarning.Output(lt.depth, fmt.Sprintf(format, v...))
	}
}

func (lt *llogT) Info(format string, v ...interface{}) {
	if levelInfo > lt.level {
		return
	}
	lt.pool.JobQueue <- func() {
		lt.wInfo.Output(lt.depth, fmt.Sprintf(format, v...))
	}
}

func (lt *llogT) Close() {
	lt.pool.Release()
	lt.lf.Close()
}
